package com.gitee.l0km.codegen.generic;

import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.gitee.l0km.codegen.annotations.AnnotationException;
import com.gitee.l0km.codegen.annotations.AnnotationRuntimeException;
import com.gitee.l0km.codegen.annotations.DefaultGenericTypes;
import com.gitee.l0km.codegen.annotations.DeriveMethod;
import com.gitee.l0km.codegen.annotations.Generic;
import com.gitee.l0km.codegen.annotations.GenericNameException;
import com.gitee.l0km.codegen.annotations.GenericParam;
import com.gitee.l0km.codegen.annotations.GenericParamException;
import com.gitee.l0km.codegen.base.AnnotationUtils;
import com.gitee.l0km.codegen.base.CodeGenUtils;
import com.gitee.l0km.codegen.base.InvalidAnnotationDefineException;
import com.gitee.l0km.codegen.base.InvalidNameException;
import com.gitee.l0km.codegen.base.Method;
import com.gitee.l0km.codegen.base.NewSourceInfoAbstract;
import com.gitee.l0km.codegen.base.ServiceInfo;
import com.gitee.l0km.codegen.base.Method.Parameter;
import com.gitee.l0km.com4j.base.Assert;

public class GenericInterface<T> extends NewSourceInfoAbstract<T> implements GenericInterfaceConstants {
	private static final Logger logger = LoggerFactory.getLogger(NewSourceInfoAbstract.class);

	private DefaultParameterGenericTypes defaultGenericTypes;

	private Class<?> shellInterface;

	private ServiceInfo serviceInfo;

	private boolean hasRemoteResolveType;

	public GenericInterface(Class<T> interfaceClass, Class<? extends T> refClass, Class<?> shellInterface) {
		super(interfaceClass, refClass, null);
		Assert.notNull(shellInterface, "shellInterface");
		this.shellInterface=shellInterface;
		
		if(!shellInterface.isInterface())
			throw new IllegalArgumentException(String.format("shellInterface must be a interface(必须是接口)"));
		if (!interfaceClass.isAssignableFrom(shellInterface))
			throw new IllegalArgumentException(String.format(
					"shellInterface must  extends from  [%s] (必须实现接口)", interfaceClass.getName()));

	}

	@Override
	public boolean compile() {
		boolean compileOk=false;
		try {
			defaultGenericTypes = new DefaultParameterGenericTypes(interfaceClass);
			serviceInfo=new ServiceInfo(AnnotationUtils.getServiceAnnotation(shellInterface));
			super.compile();
			importedList.remove(refClass.getSimpleName());
			importedList.remove(interfaceClass.getSimpleName());
			// 检查是否有同名的泛型名称
			for (String name : defaultGenericTypes.names()) {
				if (hasInClassGenericTypes(name))
					try {
						throw new GenericNameConflictException(name, DefaultGenericTypes.class.getSimpleName(),
								defaultGenericTypes.toString());
					} catch (GenericNameConflictException e) {
						throw new DefaultGenericTypesExceptoin(e);
					}
			}
			for (Method method : methodsNeedGenerated) {
				compile(method);
			}
			compileOk=true;
		} catch (AnnotationException e) {
			logger.error(e.toString());
		}catch(AnnotationRuntimeException e){
			logger.error(e.toString());
		}
		return compileOk;
	}

	private final void compile(Method method) throws GenericParamException, GenericNameException, GenericNameConflictException {
		for (Parameter param : method.getParameters()) {
			try {
				compile(param);
			} catch (GenericParamRedefineException e) {
				logger.warn("{} is generic type({}) defined by class,can't be redefined in method:\n{}", param.name,
						param.genericType.toString(), method.toGenericString());
			} catch (GenericNameException e) {
				throw new GenericNameException(String.format(
						"the name of annotation %s of parameter [%s] must not have space char in method %s",
						Generic.class.getSimpleName(), param.name, method.toGenericString()));
			}
		}
		DeriveMethod deriveMethodAnnotation = getDeriveMethodAnnotation(method);
		if(deriveMethodAnnotation!=null){
			this.addImportedClass(deriveMethodAnnotation.localResolvedTypes());			
			if(deriveMethodAnnotation.remoteResolveTypes().length>0){
				hasRemoteResolveType=true;
				this.addImportedClass(deriveMethodAnnotation.remoteResolveTypes());
			}
		}
	}

	private final void compile(Parameter param) throws GenericParamRedefineException, GenericNameConflictException,
			GenericNameException {
		GenericParam genericParam = param.getAnnotation(GenericParam.class);
		if (null != genericParam && genericParam.value() && (param.genericType instanceof TypeVariable)) {
			throw new GenericParamRedefineException();
		}
		if (null != genericParam && !genericParam.name().isEmpty()) {
			if (!Pattern.compile("^[A-Z]\\w*$").matcher(genericParam.name()).matches())
				throw new GenericNameException();
			if (hasInClassGenericTypes(genericParam.name()))
				throw new GenericNameConflictException(genericParam.name(), GenericParam.class.getSimpleName(),
						genericParam.toString());
		}
	}

	@Override
	protected void createMethodsNeedGenerated() {
		ArrayList<java.lang.reflect.Method> interfaceMethods = new ArrayList<java.lang.reflect.Method>(
				Arrays.asList(interfaceClass.getMethods()));
		Iterator<java.lang.reflect.Method> it = interfaceMethods.iterator();
		try {
			Method cm;
			while (it.hasNext()) {
				java.lang.reflect.Method im = it.next();
//				cm = new Method(refClass.getMethod(im.getName(), im.getParameterTypes()),
//						this.paramTable.getParameterNames(im.getName(), im.getParameterTypes()));				
				cm = new Method(im,
						this.paramTable.getParameterNames(im.getName(), im.getParameterTypes()));				

				if (needGeneric(cm))
					this.methodsNeedGenerated.add(cm);
			}
		} catch (NoSuchMethodException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * @return defaultGenericTypes
	 */
	public DefaultParameterGenericTypes getDefaultGenericTypes() {
		return defaultGenericTypes;
	}

	private boolean hasInClassGenericTypes(String name) {
		for (TypeVariable<Class<T>> type : interfaceClass.getTypeParameters()) {
			if (type.getName().equals(name))
				return true;
		}
		return false;
	}

	private final boolean hasNeedGenericParameter(Method method) {
		for (Parameter param : method.getParameters()) {
			if (needGeneric(param)) {
				return true;
			}
		}
		return false;
	}

	public boolean needGeneric(Method method) {
		Generic generic = method.getAnnotation(Generic.class);
		return (null == generic || generic.value()) && hasNeedGenericParameter(method);
	}

	public final boolean needGeneric(Parameter param){
		GenericParam genericParam = param.getAnnotation(GenericParam.class);
		return (null == genericParam || genericParam.value()) && defaultGenericTypes.hasType(param.type);
	}
	public final GenericParam getGenericParam(Parameter param){
		return  param.getAnnotation(GenericParam.class);
	}
	
	public final Set<String> getGenericNames(Method method) {
		GenericParam genericParam;
		Set<String> names=new HashSet<String>();
		for (Parameter param : method.getParameters()) {
			if (needGeneric(param)) {
				if(null==(genericParam = getGenericParam(param))){					
					names.add(defaultGenericTypes.getName(param.getType()));
				}else
					names.add(genericParam.name());
			}
		}
		return names;
	}
	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder();
		int c = 0;
		builder.append("//classes need imported in new Class:\n");
		for (Class<?> clazz : importedList.values()) {
			builder.append("//[" + (++c) + "]\nimported ").append(clazz.getName()).append(";\n");
		}
		builder.append("public interface NewClass{\n");
		c = 0;
		builder.append("//methods thad need generated in new Class:\n");
		for (Method m : methodsNeedGenerated) {
			builder.append("//[" + (++c) + "]\n").append(m.toGenericString()).append(";\n");
		}
		builder.append("}\n");
		return builder.toString();
	}

	/**
	 * @return shellInterface
	 */
	public Class<?> getShellInterface() {
		return shellInterface;
	}
	public DeriveMethod getDeriveMethodAnnotation(Method method) {
		try {
			return AnnotationUtils.getDeriveMethodAnnotation(method, serviceInfo);
		} catch (InvalidNameException e) {
			throw new RuntimeException(e);
		} catch (InvalidAnnotationDefineException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * @return serviceInfo
	 */
	public ServiceInfo getServiceInfo() {
		return serviceInfo;
	}

	public boolean isTargetType(Class<?> type){
		return serviceInfo.getTargetType()==type;
	}
	public boolean needDeriveParam(Method method,Parameter param){
		DeriveMethod deriveMethod =getDeriveMethodAnnotation(method);
		if(deriveMethod!=null){
			Set<String> set = CodeGenUtils.toSet(deriveMethod.genericParam());
			return needGeneric(param)&&(set.isEmpty()||set.contains(param.name));
		}
		return false;
	}
	public ArrayList<String> getDeriveParameterNames(Method method){
		ArrayList<String> list = new ArrayList<String>();
		for(Parameter parameter:method.getParameters()){
			if(needDeriveParam(method,parameter)){
				list.add(parameter.name);
			}
		}
		return list;
	}
	/**
	 * @return hasRemoteResolveType
	 */
	public boolean isHasRemoteResolveType() {
		return hasRemoteResolveType;
	}

}
